package com.jplus.core.core.abstracts.defaults;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jplus.core.anno.com.Autowired;
import com.jplus.core.anno.com.Qualifier;
import com.jplus.core.anno.com.Scope.EScope;
import com.jplus.core.anno.com.Value;
import com.jplus.core.core.ApplicationContext;
import com.jplus.core.core.abstracts.BeanDefinitions;
import com.jplus.core.core.abstracts.BeanFactory;
import com.jplus.core.core.abstracts.Environment.FRAME;
import com.jplus.core.core.beans.BeanDefinition;
import com.jplus.core.core.beans.BeanKey;
import com.jplus.core.core.beans.BeanWrapper;
import com.jplus.core.core.events.ApplicationEvent;
import com.jplus.core.core.events.ApplicationListener;
import com.jplus.core.fault.BeansException;
import com.jplus.core.utill.Assert;
import com.jplus.core.utill.ConvertUtil;
import com.jplus.core.utill.JUtil;

public class DefaultBeanFactory extends BeanFactory {
	protected Logger log = LoggerFactory.getLogger(DefaultBeanFactory.class);

	protected Map<Class<? extends ApplicationEvent>, Object> eventBean;// EventBean
	private ConvertUtil convert = ConvertUtil.newInstance();
	private BeanDefinitions beanDefinition;

	public ApplicationContext getApplicationContext() {
		return super.context;
	}

	public DefaultBeanFactory(ApplicationContext context) {
		super.beans = new ConcurrentHashMap<>();
		this.eventBean = new ConcurrentHashMap<>();
		super.thBeans = new ThreadLocal<>();
		super.context = context;
		this.beanDefinition = context.getBeanDefinitions();
	}

	@Override
	public void removeBean(BeanDefinition bd) {
		beanDefinition.removeBeanDefinition(bd.getBeanKey());
		beans.remove(bd.getBeanKey());
		thBeans.remove();
	}

	@Override
	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
		return this.getBean(beanDefinition.getBeanKey(name, requiredType));
	}

	@Override
	public Object getBean(String name) {
		return this.getBean(beanDefinition.getBeanKey(name, null));
	}

	@Override
	public <T> T getBean(Class<T> beanClass) {
		return this.getBean(beanDefinition.getBeanKey(null, beanClass));
	}

	public void buildBean() {
		List<BeanDefinition> bds = beanDefinition.listSortBeanDefinitions();
		Boolean lazy_init = JUtil.toBoolean(context.getEnvironment().getProp(FRAME.LAZY_INIT, "false"));
		if (!lazy_init)
			for (BeanDefinition entry : bds)
				buildBean(entry);
	}

	public void buildBean(BeanDefinition bd) {
		if (!bd.isLazyInit())
			getBean(bd.getBeanKey());
	}

	@SuppressWarnings("unchecked")
	public <T> T getBean(BeanKey key) {
		BeanDefinition bd = null;
		bd = beanDefinition.getBeanDefinition(key);
		if (bd == null) {
			log.error("GetBean Exception : " + key.toString());
			throw new IllegalArgumentException();
		}
		BeanWrapper<T> bw = null;
		if (bd.getScope() == EScope.SINGLETON) {
			bw = (BeanWrapper<T>) beans.get(key);
			if (bw == null)
				bw = doGetSingleton(key, bd);// 全局共享，单例
		} else if (bd.getScope() == EScope.REQUEST) {
			if (thBeans.get() != null)
				bw = (BeanWrapper<T>) thBeans.get().get(key);
			if (bw == null)
				bw = doGetRequestBean(key, bd);// 线程内共享
		} else if (bd.getScope() == EScope.PROTOTYPE) {
			bw = doCreateBean(key, bd);// 每次都创建
		}
		Assert.notNull(bw);
		return bw.getBean();
	}

	public void addSingletonBeans(BeanKey bk, BeanWrapper<?> bw) {
		beans.put(bk, bw);
	}

	/**
	 * 获取【单例】作用域对象：
	 */
	private <T> BeanWrapper<T> doGetSingleton(BeanKey key, BeanDefinition bd) {
		synchronized (bd) {
			BeanWrapper<T> bw = doCreateBean(key, bd);
			beans.put(key, bw);
			return bw;
		}
	}

	/**
	 * 获取【线程】作用域对象：
	 */
	private <T> BeanWrapper<T> doGetRequestBean(BeanKey key, BeanDefinition bd) {
		BeanWrapper<T> bw = doCreateBean(key, bd);
		Map<BeanKey, BeanWrapper<?>> map = thBeans.get();
		if (map == null)
			map = new ConcurrentHashMap<>();
		map.put(key, bw);
		thBeans.set(map);
		return bw;
	}

	/**
	 * 创建实例
	 */
	private <T> BeanWrapper<T> doCreateBean(BeanKey key, BeanDefinition bd) {
		BeanWrapper<T> bw = context.getFactoryBean().getObject(bd);
		List<BeanKey> dbs = bd.getDependences();
		if (dbs != null && dbs.size() > 0) {
			for (BeanKey dc : dbs) {
				getBean(dc);
			}
		}
		doPropertyInject(bd, bw);
		return bw;
	}

	/**
	 * 执行注入
	 */
	@SuppressWarnings("unchecked")
	private <T> void doPropertyInject(BeanDefinition bd, BeanWrapper<T> bw) {
		// 1.Field
		if (bd.getResourceFields() != null && bd.getResourceFields().size() > 0)
			for (Field f : bd.getResourceFields()) {
				Object obj = null;
				if (f.isAnnotationPresent(Value.class)) {// 配置文件注入
					String name = f.getAnnotation(Value.class).value();
					obj = context.getEnvironment().getProp(name, name);
					obj = convert.convert((obj), f.getType());
				}
				if (f.isAnnotationPresent(Autowired.class)) {// 依赖对象注入
					String name = null;
					if (f.isAnnotationPresent(Qualifier.class))
						name = f.getAnnotation(Qualifier.class).value();
					else
						name = f.getAnnotation(Autowired.class).value();
					BeanKey bk = new BeanKey(name, f.getType());
					BeanWrapper<T> dbw = (BeanWrapper<T>) beans.get(bk);
					obj = dbw.getBean();
				}
				try {
					f.setAccessible(true);
					f.set(bw.getBean(), obj);// 注入对象
				} catch (Exception e) {
					e.printStackTrace();
					log.error("doPropertyInject Exception:" + f, e);
				}
			}

	}

	@Override
	public boolean buildBean(BeanKey key, Object bean, EScope scope) {
		beanDefinition.buildBeanDefinition(key.getName(), key.getBeanClass());
		switch (scope) {
		case SINGLETON:
			beans.put(key, new BeanWrapper<Object>(bean, null));
			break;
		case REQUEST:
			thBeans.get().put(key, new BeanWrapper<Object>(bean, null));
			break;
		default:
			return false;
		}
		return true;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends ApplicationListener<ApplicationEvent>> T getEventBean(ApplicationEvent event) {
		Object bean = eventBean.get(event.getClass());
		if (bean == null) {
			synchronized (this) {
				List<BeanDefinition> bds = beanDefinition.listSortBeanDefinitions();
				for (BeanDefinition bd : bds) {
					Class<? extends ApplicationEvent> clazz = bd.getEventClass();
					if (event.getClass() == clazz) {
						Object obj = getBean(bd.getBeanKey());
						eventBean.put(clazz, obj);
						return (T) obj;
					}
				}
			}
		}
		return null;
	}

}
